package cn.chiship.sdk.core.encryption;

import cn.chiship.sdk.core.base.constants.BaseConstants;
import cn.chiship.sdk.core.encryption.rsa.RsaSignature;
import cn.chiship.sdk.core.enums.HeaderEnum;
import cn.chiship.sdk.core.exception.custom.BusinessException;
import cn.chiship.sdk.core.util.http.RequestUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * 加密工具
 *
 * @author lj
 */
public class EncryptionUtil {

	private static final Logger LOGGER = LoggerFactory.getLogger(EncryptionUtil.class);

	private EncryptionUtil() {

	}

	/**
	 * 加密
	 * @param content 内容
	 * @return 结果
	 * @throws Exception 异常
	 */
	public static String encryption(String content) {
		return encryption(content, "", SignType.MD5);
	}

	public static String encryption(String content, SignType signType) {
		return encryption(content, "", signType);
	}

	public static String encryption(String content, String key) {
		return encryption(content, key, SignType.MD5);
	}

	public static String encryption(String content, String key, SignType signType) {
		if (SignType.MD5.equals(signType)) {
			return md5(content + key).toLowerCase();
		}
		else if (SignType.HMACSHA256.equals(signType)) {
			return hMacSha256(content, key);
		}
		else {
			throw new BusinessException("无效的签名方式:" + signType);
		}
	}

	public static String paramEncryption(List<String> data, String key, Integer encryptionCount) {
		return paramEncryption(data, key, encryptionCount, SignType.MD5);
	}

	/**
	 * 参数加密
	 * @param data 元数据
	 * @param key 加密key
	 * @param encryptionCount 加密次数
	 * @param signType 加密类型
	 * @return 结果
	 * @throws Exception 异常
	 */
	public static String paramEncryption(List<String> data, String key, Integer encryptionCount, SignType signType) {

		String[] parameterValues = new String[data.size()];
		for (int i = 0, j = data.size(); i < j; i++) {
			parameterValues[i] = data.get(i);
		}
		Arrays.sort(parameterValues);
		StringBuilder parameterValue = new StringBuilder();
		for (int i = 0; i < parameterValues.length; i++) {
			parameterValue.append(parameterValues[i]);
		}

		if (SignType.MD5.equals(signType)) {
			if (key == null) {
				parameterValue.append("");
			}
			else {
				parameterValue.append(key);
			}
			LOGGER.info("参与加密参数:{}", parameterValue);
			String tempEncrypt = md5(parameterValue.toString()).toLowerCase();
			for (int i = 0; i < encryptionCount - 1; i++) {
				tempEncrypt = md5(tempEncrypt).toLowerCase();
			}
			return tempEncrypt;
		}
		else if (SignType.HMACSHA256.equals(signType)) {
			String tempEncrypt = hMacSha256(parameterValue.toString(), key).toLowerCase();
			for (int i = 0; i < encryptionCount - 1; i++) {
				tempEncrypt = hMacSha256(tempEncrypt, key).toLowerCase();
			}
			return tempEncrypt;
		}
		else {
			throw new BusinessException("无效的签名方式" + signType);
		}
	}

	/**
	 * RSA签名
	 * @param params 参数
	 * @param key 钥匙
	 * @param privateKey 秘钥
	 * @return 结果
	 * @throws IOException 异常
	 */
	public static String rsaSignature(List<String> params, String key, String privateKey) {

		String[] parameterValues = new String[params.size()];
		for (int i = 0, j = params.size(); i < j; i++) {
			parameterValues[i] = params.get(i);
		}
		Arrays.sort(parameterValues);
		StringBuilder parameterValue = new StringBuilder();
		for (int i = 0; i < parameterValues.length; i++) {
			parameterValue.append(parameterValues[i]);
		}
		if (key == null) {
			parameterValue.append("");
		}
		else {
			parameterValue.append(key);
		}
		LOGGER.info("参与签名参数:{}", parameterValue);
		return RsaSignature.sign(parameterValue.toString(), privateKey);
	}

	/**
	 * RSA验签
	 * @param request 请求
	 * @param key 钥匙
	 * @param publicKey 公钥
	 * @return 结果
	 */
	public static boolean rsaVerifySignature(HttpServletRequest request, String key, String publicKey)
			throws IOException {
		String sign = request.getHeader(HeaderEnum.HEADER_SIGN.getName());
		List<String> data = RequestUtil.getParameterList(request);
		data.addAll(RequestUtil.getRequestBodyList(request));
		String[] parameterValues = new String[data.size()];
		for (int i = 0, j = data.size(); i < j; i++) {
			parameterValues[i] = data.get(i);
		}
		Arrays.sort(parameterValues);
		StringBuilder parameterValue = new StringBuilder();
		for (int i = 0; i < parameterValues.length; i++) {
			parameterValue.append(parameterValues[i]);
		}
		if (key == null) {
			parameterValue.append("");
		}
		else {
			parameterValue.append(key);
		}
		LOGGER.debug("参与验签参数:{}", parameterValue);
		return RsaSignature.doCheck(parameterValue.toString(), sign, publicKey);
	}

	/**
	 * 获得签名
	 * @param data 数据集合
	 * @param key 钥匙
	 * @return 结果
	 * @throws Exception 异常
	 */
	public static String generateSignature(Map<String, String> data, String key) {
		return generateSignature(data, key, SignType.MD5);
	}

	public static String generateSignature(Map<String, String> data, String key, SignType signType) {
		Set<String> keySet = data.keySet();
		String[] keyArray = keySet.toArray(new String[keySet.size()]);
		Arrays.sort(keyArray);
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < keyArray.length; ++i) {
			String k = keyArray[i];
			if (!"sign".equals(k) && (data.get(k)).trim().length() > 0) {
				sb.append(k).append("=").append((data.get(k)).trim()).append("&");
			}
		}
		sb.append("key=").append(key);
		if (SignType.MD5.equals(signType)) {
			return md5(sb.toString()).toLowerCase();
		}
		else if (SignType.HMACSHA256.equals(signType)) {
			return hMacSha256(sb.toString(), key);
		}
		else {
			throw new BusinessException("无效的签名方式" + signType);
		}
	}

	/**
	 * MD5加密
	 * @param content 内容
	 * @return 结果
	 */
	private static String md5(String content) {
		try {
			return md5(content, BaseConstants.UTF8);
		}
		catch (Exception e) {
			return "";
		}
	}

	private static String md5(String origin, String charsetName) {
		try {
			String resultString = origin;
			MessageDigest md = MessageDigest.getInstance("MD5");
			if (charsetName == null || "".equals(charsetName)) {
				resultString = byteArrayToHexString(md.digest(resultString.getBytes()));
			}
			else {
				resultString = byteArrayToHexString(md.digest(resultString.getBytes(charsetName)));
			}
			return resultString.toLowerCase();
		}
		catch (Exception e) {
			return "";
		}
	}

	private static String hMacSha256(String data, String key) {
		try {
			Mac sha256Hmac = Mac.getInstance("HmacSHA256");
			SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
			sha256Hmac.init(secretKey);
			byte[] array = sha256Hmac.doFinal(data.getBytes(StandardCharsets.UTF_8));
			StringBuilder sb = new StringBuilder();
			for (int i = 0; i < array.length; ++i) {
				byte item = array[i];
				sb.append(Integer.toHexString(item & 255 | 256).substring(1, 3));
			}

			return sb.toString().toLowerCase();
		}
		catch (Exception e) {
			throw new BusinessException("hMACSha256发生了错误!");
		}
	}

	private static String byteArrayToHexString(byte[] b) {
		StringBuilder resultSb = new StringBuilder();
		for (int i = 0; i < b.length; i++) {
			resultSb.append(byteToHexString(b[i]));
		}
		return resultSb.toString();
	}

	private static String byteToHexString(byte b) {
		int n = b;
		if (n < 0) {
			n += 256;
		}
		int d1 = n / 16;
		int d2 = n % 16;
		return HEX_DIGITS[d1] + HEX_DIGITS[d2];
	}

	private static final String[] HEX_DIGITS = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d",
			"e", "f" };

	public enum SignType {

		/**
		 * md5加密
		 */
		MD5,
		/**
		 * sha256加密
		 */
		HMACSHA256;

		private SignType() {
		}

	}

}
